#include "h264encoder.h"
#include"dlog.h"

H264Encoder::H264Encoder()
{

}

H264Encoder::~H264Encoder()
{
    if(_ctx)
    {
        avcodec_free_context(&_ctx);
    }
    if(_frame)
    {
        av_frame_free(&_frame);
    }
}

int H264Encoder::init(const Properties &properties)
{
    int ret = 0;
    _width = properties.GetProperty("width", 0);
    if(_width == 0 || _width % 2 == 1)
    {
        LogError("width is valied: %d", _width);
        return RET_ERR_NOT_SUPPORT;
    }
    _height = properties.GetProperty("height", 0);
    if(_height == 0 || _height % 2 == 1)
    {
        LogError("height is valied: %d", _height);
        return RET_ERR_NOT_SUPPORT;
    }

    _fps = properties.GetProperty("fps", 25);
    _b_frames = properties.GetProperty("b_frames", 0);
    _bit_rate = properties.GetProperty("bit_rate", 500*1024);
    _gop = properties.GetProperty("gop", _fps);
    _pix_fmt = properties.GetProperty("pix_fmt", AV_PIX_FMT_YUV420P);
    _codec_name = properties.GetProperty("codec_name", "default");

    // 查找H264编码器
    if(_codec_name == "default")
    {
        LogInfo("use default video encoder");
        _codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    }
    else
    {
        LogInfo("use %s video encoder", _codec_name.c_str());
        _codec = avcodec_find_encoder_by_name(_codec_name.c_str());
    }
    if(!_codec)
    {
        LogError("cannot find video encoder");
        return RET_FAIL;
    }
    _ctx = avcodec_alloc_context3(_codec);
    if(!_ctx)
    {
        LogError("video ctx cannot avcodec_alloc_context3");
        return RET_FAIL;
    }
    // 设置编码器上下文中的基础参数
    _ctx->width = _width;
    _ctx->height = _height;
    _ctx->bit_rate = _bit_rate;
    _ctx->gop_size = _gop;
    // 帧率
    _ctx->framerate.num = _fps; //分子
    _ctx->framerate.den = 1; //分母
    // time_base
    _ctx->time_base.num = 1; //分子
    _ctx->time_base.den = _fps; //分母
    _ctx->pix_fmt = (enum AVPixelFormat)_pix_fmt;
    // 音频没加这个，视频为什么要加？可不可以不加？ fix me
    _ctx->codec_type = AVMEDIA_TYPE_VIDEO;
    _ctx->max_b_frames = _b_frames;
    // 设置编码算法的参数
    av_dict_set(&_dict, "preset", "medium", 0);
    av_dict_set(&_dict, "tune", "zerolatency", 0);
    av_dict_set(&_dict, "profile", "high", 0);
    // 当SPS、PPS只放在第一帧的时候，配置该参数。在预览视频的时候可以看到第一帧的画面
    _ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    // 打开编码器
    ret = avcodec_open2(_ctx, _codec, &_dict);
    if(ret < 0)
    {
        char buf[1024] = { 0 };
        av_strerror(ret, buf, sizeof(buf) - 1);
        LogError("avcodec_open2 failed:%s", buf);
        return RET_FAIL;
    }

    // 需要从编码器中读取SPS、PPS
    if(_ctx->extradata)
    {
        LogInfo("extradata_size: %d", _ctx->extradata_size);
        /// H264的NALU有一个起始单位是 0x00 00 00 01
        /// 故 sps 是从 第4个字节开始
        uint8_t *sps = _ctx->extradata + 4;
        int sps_len = 0;
        uint8_t *pps = NULL;
        int pps_len = 0;
        uint8_t *data = _ctx->extradata + 4;
        // sps 和 pps 中间间隔的是 0x00 00 00 01四个字节
        for(int i = 0; i < _ctx->extradata_size - 4; i++)
        {
            if(0 == data[i] && 0 == data[i + 1] && 0 == data[i + 2] && 1 == data[i + 3])
            {
                pps = &data[i + 4];
                break;
            }
        }
        sps_len = int(pps - sps) - 4;// 4 是 00 00 00 01占用的字节
        pps_len = _ctx->extradata_size - 4 * 2 - sps_len;
        _sps.append(sps, sps + sps_len);
        _pps.append(pps, pps + pps_len);
    }
    _frame = av_frame_alloc();
    if(!_frame)
    {
        LogError("av_frame_allocfailed");
        return RET_ERR_OUTOFMEMORY;
    }
    _frame->width = _width;
    _frame->height = _height;
    _frame->format = _ctx->pix_fmt;
    // 给该帧分配buf
    ret = av_frame_get_buffer(_frame, 0);
    if(ret < 0)
    {
        LogError("video_frame av_frame_get_buffer failed");
        return RET_FAIL;
    }
    return RET_OK;

}

AVPacket *H264Encoder::encode(uint8_t *yuv, int size, int64_t pts, int *pkt_frame, RET_CODE *ret)
{
    int ret1 = 0;
    *ret = RET_OK;
    *pkt_frame = 0;
    if(yuv)
    {
        int need_size = 0;
        need_size = av_image_fill_arrays(_frame->data, _frame->linesize, yuv,
                                         (enum AVPixelFormat)_frame->format,
                                         _frame->width, _frame->height, 1);
        if(need_size != size)
        {
            LogError("need_size:%d != size:%d", need_size, size);
            *ret = RET_FAIL;
            return NULL;
        }
        _frame->pts = pts;
        _frame->pict_type = AV_PICTURE_TYPE_NONE;//不指定该帧的类型。todo：如果我指定为I帧呢，会不会全部生成I帧，和gop有冲突吗
        ret1 = avcodec_send_frame(_ctx, _frame);
    }
    else
    {
        // 冲刷编码器中的剩余数据【调用方控制，但只能生效一次】
        ret1 = avcodec_send_frame(_ctx, NULL);
    }
    if(ret1 < 0) //报错，传入的帧无法正常编码
    {
        char buf[1024] = {0};
        av_strerror(ret1, buf, sizeof(buf) - 1);
        LogError("avcodec_send_frame failed:%s", buf);
        *pkt_frame = 1;//标记帧报错
        if(ret1 == AVERROR(EAGAIN))//frame无法写入编码器了，即编码器中的数据满了，现在需要的马上读取packet数据
        {
            *ret = RET_ERR_EAGAIN;
            return NULL;
        }
        else if(ret1 == AVERROR_EOF) //文件读取结束
        {
            *ret = RET_ERR_EOF;
            return NULL;
        }
        else // 真正报错了
        {
            *ret = RET_FAIL;
            return NULL;
        }
    }
    AVPacket *packet = av_packet_alloc();
    ret1 = avcodec_receive_packet(_ctx, packet);
    if(ret1 < 0)
    {
        LogError("H264: avcodec_receive_packet ret:%d", ret1);
        av_packet_free(&packet);
        *pkt_frame = 0;
        if(ret1 == AVERROR(EAGAIN)) // 本次发送的帧无法解析出一个完整的packet对象，需要继续发送frame我们才有packet读取
        {
            *ret = RET_ERR_EAGAIN;
            return NULL;
        }
        else if(ret1 == AVERROR_EOF) // 不能在读取出来packet来了
        {
            *ret = RET_ERR_EOF;
            return NULL;
        }
        else  // 真正报错，这个encoder就只能销毁了
        {
            *ret = RET_FAIL;
            return NULL;
        }
    }
    else
    {
        *ret = RET_OK;
        return packet;
    }
}
